/**
 * Libevent例程：实现打印HelloWorld的服务器
 * @author Martin
 * @date 2021-8-10
 * @note 
 * 编译:
 * $ gcc -o server main.c -levent
 * 终端输入测试：
 * $ nc 127.0.0.1 9995
 */
#include <stdio.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <string.h>
#include <signal.h>
#include <event2/buffer.h>

static const char MESSAGE[] = "Hello, World!\n";
static const int PORT = 9995;

static void listener_cb(struct evconnlistener *, evutil_socket_t,
        struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);


int main_helloworld(int argc, char *argv[])
{
    struct event_base *base; /* 每个base持有一个事件集合 */
    struct evconnlistener *listener; /* 连接监听器 */
    struct event *signal_event;

    struct sockaddr_in sin; /* 服务器IPv4地址 */

    base = event_base_new(); /* 创建event_base对象，监控事件集合 */
    if (!base) { /* erorr */
        fprintf(stderr, "Could not initialize libevent!\n");
        return 1;
    }

    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);

    /* 创建TCP连接监听器 */
    listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
                                       LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
                                       (struct sockaddr *)&sin, sizeof(sin));
    if (!listener) {
        fprintf(stderr, "Could not create a listener!\n");
        return 1;
    }

    signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); /* 构造信号事件处理器，指定要监听的信号和信号处理函数(回调) */
    if (!signal_event || event_add(signal_event, NULL) < 0) { /* 将signal_event 添加到event_base */
        fprintf(stderr, "Could not create/add a signal event!\n");
        return 1;
    }

    /* 运行事件循环, 等待事件就绪 */
    event_base_dispatch(base); /* <=> event_base_loop(base, 0) */

    /* 释放资源 */
    evconnlistener_free(listener);
    event_free(signal_event);
    event_base_free(base);

    printf("done\n");
    return 0;
}

/**
 * TCP连接回调函数，监听到连接请求时调用
 * @param evconnlistener 连接监听器
 * @param fd 连接的文件描述符
 * @param sa 连接的源地址（客户端地址）
 * @param socklen sa的长度（取决于其地址类型）
 * @param user_data 用户数据
 */
static void
listener_cb(struct evconnlistener *evconnlistener, evutil_socket_t fd,
        struct sockaddr *sa, int socklen, void *user_data)
{
    struct event_base *base = user_data;
    struct bufferevent *bev;

    struct sockaddr_in *psin = (struct sockaddr_in *)sa;
    char ipv4[INET_ADDRSTRLEN];
    evutil_inet_ntop(AF_INET, &psin->sin_addr, ipv4, sizeof(ipv4));
    printf("recv connect request from %s:%u\n", ipv4, ntohs(psin->sin_port));

    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        fprintf(stderr, "Error constructing bufferevent!");
        event_base_loopbreak(base);
        return;
    }

    bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL); /* 修改bufferevent多个回调：在已经读取足够数据、写入足够数据、发生错误调用时被调用 */
    bufferevent_enable(bev, EV_WRITE); /* 启用bufferevent上的EV_WRITE事件 */
    bufferevent_disable(bev, EV_READ); /* 禁用bufferevent上的EV_READ事件 */

    bufferevent_write(bev, MESSAGE, strlen(MESSAGE)); /* 将内存中MESSGAE字符串添加到输出缓冲区末尾 */
}

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
    struct evbuffer *output = bufferevent_get_output(bev);

    if (evbuffer_get_length(output) == 0) {
        printf("flush answer\n");
        bufferevent_free(bev);
    }
}

static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
    if (events & BEV_EVENT_EOF) {
        printf("Connection closed.\n");
    }
    else if (events & BEV_EVENT_ERROR) {
        printf("Got an error on the connection: %s\n", strerror(errno));
    }

    bufferevent_free(bev);
}

static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
    struct event_base *base = user_data;
    struct timeval delay = {2, 0};

    printf("Caught an interrupt signal: exiting cleanly in two seconds.\n");

    event_base_loopexit(base, &delay);
}
